함수 템플릿(Fucntion Template)무한한 수의 함수 오버로드를 생성하는 청사진이다.
아래의 타입만 다르고, 동일한 동작을 하는 오버로딩을 분리해서 작성할 수 있다.
int max(int a, int b){
if(a>b) return a;
else return b;
}
double max(double a, double b){
if(a>b) return a;
else return b;
}
위의 두 함수는 템플릿을 이용하여 하나의 함수 본문으로 작성할 수 있다.
#include <iostream>
template<typename T>
T max(T a, T b){
if(a>b) return a;
else return b;
}
int main(void){
std::cout<<"The maximum of 3 and 5 is "<<max(3, 5)<<'\n';
std::cout<<"The maximum of 3l and 5l is "<<max(3l, 5l)<<'\n';
std::cout<<"The maximum of 3.0 and 5.0 is "<<max(3.0, 5.0)<<'\n';
unsigned u1=2, u2=8;
std::cout<<"The maximum of u1*u2, u1+u2 is "<<max(u1*u2, u1+u2)<<'\n';
}
4번의 max() 함수 호출은 아래와 같이 인스턴스화 된다.
int max(int, int);
long max(long, long);
double max(double, double);
unsigned max(unsigned, unsigned);
인스턴스화(Instantiation)컴파일러는 제네릭이 아닌 함수의 경우, 정의를 읽고 오류를 검사한 후 실행 가능한 코드를 생성한다.
컴파일러가 제네릭 함수 정의를 처리할 경우, 구문 분석 오류처럼
매개변수와 독립적인 오류만 검출 가능하다.
template<typename T>
inline T max(T a, T b){
return x<y?y.value:x.value;
}
위 코드는 int나 double과 같은 내장 타입으로 호출이 불가능하다.
하지만 위 코드는 컴파일된다.
(함수 타입은 내장 타입을 위한 것이 아니며, 실제 인자 타입과 함께 동작할 수 있다.)
함수 템플릿 자체만은 컴파일하더라도 바이너리에서 코드를 생성하지 않는다.
함수 템플릿을 호출할 때만, 코드를 생성한다.
(함수 템플릿을 인스턴스화함)
이 후, 컴파일러는 제네릭 함수가 주어진 인수 타입에 대한 오류를 검사한다.
템플릿을 지정된 타입으로 인스턴스화(명시적 인스턴스화)
std::cout<<max<float>(8.1, 9.3)<<'\n';
위처럼 float을 명시해줄 경우, 템플릿은 지정된 타입으로 명시적 인스턴스화 한다.
template short max<short>(short, short);
위 처럼 템플릿함수를 명시적으로 인스턴스화 할경우,
함수 호출 없이 인스턴스화를 강제할 수 있다.
매개 타입 추론인자를 값에 의한 전달
Lvalue 레퍼런스에 의한 전달
Rvalue 레퍼런스에 의한 전달
값 매개변수
template <typename T>
T max(T a, T b);
template <typename T>
T max(const T& a, const T& b);
사용되는 파라미터가 동일한 경우
template <typename TPara>
void f1(TPara P){}
int main(void){
int i=0;
int& j=i;
const int& k=i;
f1(3);
f1(i);
f1(j);
f1(k);
}
위와 같이 값으로 사용한 경우, 복사 생성자를 사용할 수 없다.
Lvalue 전달할 수 없다. Rvalue만 전달 가능
함수 매개변수가 한정이 없는 타입 매개변수인 경우,
TPara는 모든 한정자가 제거된 인수 타입이 된다.
unique_ptr<int> up;
f1(move(up));
Lvalue 레퍼런스 매개변수모든 인수를 받을 수 있도록 상수 레퍼런스를 매개변수로 사용
template <typename TPara>
void f2(const TPara& p){}
template <typename TPara>
void f3(TPara& p){}
int main(void){
int i=0;
int& j=i;
const int& k=i;
f2(i);
f2(j);
f2(k);
}
f3는 매개변수가 상수가 아닌 레퍼런스를 받는다.
f3는 참조할 수 없기 때문에 모든 리터널과 임시 변수를 거부한다.
(TPara&를 int&&가 되도록 하는 TPara 타입이 없음)
f2에 int 가 전달 될 때, TPara는 int로 치환되고 p의 타입은 int&
f2에 int&가 전달 될 때, TPara는 int로 치환되고 p의 타입은 int&
f2에 const int&가 전달 될 때, TPara는 const int로 치환되고 p의 타입은 const int&
타입 패턴 TPara&는 인수를 변경 가능한 레퍼런스로 제한하지 않는다.
포워드 레퍼런스(Rvalue+Lvalue)T&& 형태의 타입 매개변수를 사용하는 Rvalue 레퍼런스는 Lvalue도 허용한다.(Universal Reference)
template <typename TPara>
void f4(TPara&& p){}
int main(void){
int i=0;
unique_ptr<int> up;
f4(3);
f4(move(i));
f4(move(up));
int& j=i;
f4(i);
f4(j);
}
f4(3)과 f4(move(i))는 TPara가 int로 치환되고, p의 타입은 int&&
f4(move(up))는 TPara가 unique_ptr<int>로 치환되고, p의 타입은 unique_ptr<int>&&로
Rvalue 레퍼런스 이다.
만일 f4를 i나 j와 같은 Lvalue 레퍼런스로 호출할 경우, 컴파일러는 이러한 인수를 템플릿 Rvalue 레퍼런스
매개변수로 사용한다.
타입 매개변수 TPara는 int&로 취환되며 p 타입 또한 int&가 된다.
레퍼런스 붕괴
|
-& |
-&& |
T& |
T& |
T& |
T&& |
T& |
T&& |
적어도 하나가 Lvalue 레퍼런스인 경우 레퍼런스는 Lvalue 레퍼런스로 붕괴된다.
템플릿이 아닌 Rvalue 레퍼런스는 Lvalue를 허용하지 않음
Widget&& var1=someWidget;
auto&& var2=var1;
template <typename T>
void f(std::vector<T>&& param);
template <typename T>
void f(T&& param);
퍼펙트 포워딩(std::forward)Rvalue & Lvalue 레퍼런스의 조건부 변환
std::forward는 Rvalue 레퍼런스를 Rvalue로 변환하고,
Lvalue 레퍼런스를 Lvalue로 변환한다.
template <typename TPara>
void f5(TPara&& p){
f4(std::forward<TPara>(p));
}
타입 혼합
unsigned u1=2;
int i=3;
std::cout<<"max of u1 and i is "<<max(int(u1), i)<<'\n';
std::cout<<"max of u1 and i is "<<max<int>(u1, i)<<'\n';
C++는 함수가 호출하는 모든 타입 또는 조합에 대한 새로운 코드를 생성한다.
(Java는 템플릿을 한 번만 컴파일하고 해당 타입으로 변환해 서로 다른 타입에 대해 실행한다)
(컴파일 시간은 빠르지만, 런타임에서 시간이 더 걸림)
유니폼 초기화템플릿에서도 동일하게 유니폼 초기화를 사용가능하다.
template <typename T>
inline T copy(const T& to_copy){
return T{ to_copy };
}
자동 리턴 타입(auto)C++14부터는 컴파일러에서 리턴 타입을 추론할 수 있다.
template <typename T, typename U>
inline auto max(T a, U b){
return a>b?a:b;
}